﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
using System.Windows.Threading;
using psafth.AMB;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Threading;
using System.Collections;
using System.Data;
using System.Globalization;
using psafth.RCTiming.Common;
using psafth.RCTiming.Main.Reporting;

namespace psafth.RCTiming.Main.Pages
{

    /// <summary>
    /// Interaction logic for RacePage.xaml
    /// </summary>
    public partial class RacePage : Page, INotifyPropertyChanged
    {
        private AMBCommunicator _decoderCommunicator;

        private MainWindow _mainWindow;

        private DispatcherTimer _dispatcherTimer;
        private DispatcherTimer _clockTimer;
        private DateTime _startTimeStamp;
        private DateTime _stopTimeStamp;
        private DateTime _finishTimeStamp;
        private int _finishTimeout = 60;

        private bool _stopSignalSounded;

        private bool _decoderTimeSynchronized = false;

        private int _decoderNoise = 0;
        private FixedSizedQueue<string> _passings = new FixedSizedQueue<string>(8);
        private double _decoderVoltage;
        private double _decoderTemperature;

        private string _heatTitle;

        public List<UIElement> PassingsElements
        {
            get
            {
                List<UIElement> tmpElements = new List<UIElement>();

                foreach (string str in _passings)
                {
                    tmpElements.Add(new Label() { Content = str, FontFamily = new FontFamily("Segoe UI Light"), FontSize = 40.0, });
                }

                return tmpElements;
            }
        }

        public bool IsFinished
        {
            get;
            set;
        }

        public TimeSpan TimeElapsed
        {
            get;
            set;
        }

        protected void UpdateTimeElapsed()
        {
            if (this._startTimeStamp.Year < 2000)
                this.TimeElapsed = new TimeSpan(0);

            this.TimeElapsed = DateTime.Now.Subtract(this._startTimeStamp);
        }

        private TimeSpan _decoderDifference
        {
            get;
            set;
        }

        public int HeatDuration
        {
            get;
            set;
        }

        public string HeatTitle
        {
            get
            {
                return _heatTitle;
            }
            set
            {
                _heatTitle = value;
                OnPropertyChanged("HeatTitle");
            }
        }

        private Window ParentWindow
        {
            get
            {
                return Window.GetWindow(this);
            }
        }

        private DateTime DecoderTime
        {
            get
            {
                return DateTime.Now.Add(_decoderDifference);
            }
        }

        public bool IsRaceFinished
        {
            get
            {
                if (_finishTimeStamp == null || _finishTimeStamp.Year < 2000)
                    return false;

                return (_finishTimeStamp.Add(_decoderDifference) <= DecoderTime);
            }
        }

        public bool IsDecoderNoiseSet
        {
            get
            {
                return (DecoderNoise > 0);
            }
        }

        public double DecoderVoltage
        {
            get
            {
                return _decoderVoltage;
            }
            set
            {
                _decoderVoltage = value;
                OnPropertyChanged("DecoderVoltage");
            }
        }

        public double DecoderTemperature
        {
            get
            {
                return _decoderTemperature;
            }
            set
            {
                this._decoderTemperature = value;
                OnPropertyChanged("DecoderTemperature");
            }
        }

        public int DecoderNoise
        {
            get
            {
                return _decoderNoise;
            }
            set
            {
                _decoderNoise = value;
                pbNoise.ToolTip = string.Format("{0} dB", _decoderNoise);

                if (_decoderNoise > pbNoise.Maximum)
                    pbNoise.Maximum = _decoderNoise;

                OnPropertyChanged("DecoderNoise");
                OnPropertyChanged("PbNoiseForeGround");
            }
        }

        public SolidColorBrush PbNoiseForeGround
        {
            get
            {
                if (_decoderNoise <= 40)
                    return Brushes.Green;

                if (_decoderNoise >= 41)
                    return Brushes.Orange;

                if (_decoderNoise >= 70)
                    return Brushes.OrangeRed;

                if (_decoderNoise >= 100)
                    return Brushes.Red;

                return Brushes.Green;
            }
        }


        public ObservableCollectionExt<HeatCompetitor> Drivers;

        public ObservableCollectionExt<HeatCompetitor> FinishedDrivers;

        public void DoManualCount(int? transponderId, bool countDown)
        {
            DoManualCount(transponderId, countDown, false);
        }

        public void DoManualCount(string licenceNumber, bool countDown, bool isPenalty)
        {
            if (!_dispatcherTimer.IsEnabled)
                return;

            int amount = 1;

            if (countDown)
                amount = -1;

            try
            {
                HeatCompetitor hc = Drivers.FirstOrDefault(d => d.LicenceNumber == licenceNumber);

                DoManualCount(hc, amount, isPenalty);
            }
            catch (NullReferenceException)
            {
                WriteErrorToConsole(string.Format("Fel: Förare med licensnummer {0} hittades inte", licenceNumber));
            }
            catch (Exception ex)
            {
                WriteErrorToConsole(ex.Message);
#if DEBUG
                throw ex;
#endif
            }
        }

        public void DoManualCount(HeatCompetitor hc, int amount, bool isPenalty)
        {
            try
            {
                if (hc == null)
                    return;

                if (amount > 0)
                {
                    hc.AddLap(_startTimeStamp, DecoderTime, 0.0, 0, 0.0, true);

                    SortSetPositionAndBeep();

                    if (DecoderTime >= _stopTimeStamp)
                    {
                        hc.IsFinished = true;
                        hc.FinishTime = (DecoderTime - _stopTimeStamp).TotalSeconds;
                    }

                    if (hc.StartNumber.HasValue)
                        AddStartNumberToPassings(hc.StartNumber.Value);

                    if (hc.AverageLapTime > 0)
                    {
                        int totalSeconds = HeatDuration * 60;

                        hc.EstimatedResult = (totalSeconds / hc.AverageLapTime).ToString("F0");
                    }
                }
                else
                {
                    if (isPenalty)
                    {
                        hc.InvalidateLastLap(Lap.InvalidationType.PENALTY);
                    }
                    else
                    {
                        hc.InvalidateLastLap(Lap.InvalidationType.CORRECTION);
                    }
                }

                string transponderId = hc.TransponderId.ToString();

                if (transponderId == "0")
                    transponderId = hc.LicenceNumber;

                List<Inline> items = new List<Inline>();
                items.Add(new Run("Race Control: ") { FontWeight = FontWeights.Bold });
                items.Add(new Run() { Text = "Korrigering båt " });
                items.Add(new Run() { Text = string.Format("{0} ", transponderId), FontSize = 16, FontWeight = FontWeights.Bold });
                items.Add(new Run() { Text = string.Format("{0} varv", amount) });
                WriteToConsole(items);
            }
            catch (Exception ex)
            {
            }
        }

        public void DoManualCount(int? transponderId, bool countDown, bool isPenalty)
        {
            if (!_dispatcherTimer.IsEnabled)
                return;

            int amount = 1;

            if (countDown)
                amount = -1;

            try
            {
                HeatCompetitor hc = Drivers.FirstOrDefault(d => d.TransponderId.Value == transponderId);

                DoManualCount(hc, amount, isPenalty);
            }
            catch (NullReferenceException)
            {
                WriteErrorToConsole(string.Format("Fel: Båt med transponder {0} hittades inte", transponderId));
            }
            catch (Exception ex)
            {
                WriteErrorToConsole(ex.Message);
#if DEBUG
                throw ex;
#endif
            }
        }

        public void DoManualCountByBoatNumber(int boatNumber, bool countDown)
        {
            if (!_dispatcherTimer.IsEnabled)
                return;

            int amount = 1;

            if (countDown)
                amount = -1;

            try
            {
                HeatCompetitor hc = Drivers.FirstOrDefault(d => d.StartNumber.Value == boatNumber);

                if (hc == null)
                    return;

                DoManualCount(hc, amount, false);
            }
            catch (NullReferenceException)
            {
                WriteErrorToConsole(string.Format("Fel: Båt {0} hittades inte", boatNumber));
            }
            catch (Exception ex)
            {
                WriteErrorToConsole(ex.Message);
#if DEBUG
                throw ex;
#endif
            }
        }

        public void WriteErrorToConsole(string exMessage)
        {
            Run item = new Run(exMessage) { Foreground = Brushes.Red };
            WriteToConsole(item);
        }

        public void WriteToConsole(string text)
        {
            WriteToConsole(new Run() { Text = text });
        }

        public void WriteToConsole(Run item)
        {
            tbConsole.Inlines.Add(string.Format("[{0}] ", DateTime.Now.ToString("HH:mm:ss")));
            tbConsole.Inlines.Add(item);
            tbConsole.Inlines.Add(Environment.NewLine);
            ScrollViewer scrollViewer = tbConsole.Parent as ScrollViewer;
            scrollViewer.ScrollToBottom();
        }

        public void WriteToConsole(List<Inline> items)
        {
            tbConsole.Inlines.Add(string.Format("[{0}] ", DateTime.Now.ToString("HH:mm:ss")));
            items.ForEach(i => tbConsole.Inlines.Add(i));
            tbConsole.Inlines.Add(Environment.NewLine);
        }

        public RacePage(MainWindow m)
        {
            InitializeComponent();
            this.DataContext = this;
            _mainWindow = m;

            this.Loaded += RacePage_Loaded;
            this.Unloaded += RacePage_Unloaded;

            Drivers = new ObservableCollectionExt<HeatCompetitor>();
            FinishedDrivers = new ObservableCollectionExt<HeatCompetitor>();

            RaceGrid.DataContext = Drivers;

            _decoderDifference = new TimeSpan();

            _decoderCommunicator = new AMBCommunicator(Properties.Settings.Default.DecoderIPAddress, Properties.Settings.Default.DecoderTcpPort);
            _decoderCommunicator.WorkerReportsProgress = true;
            _decoderCommunicator.WorkerSupportsCancellation = true;
            _decoderCommunicator.ProgressChanged += _decoderListener_ProgressChanged;
            _decoderCommunicator.RunWorkerCompleted += _decoderListener_RunWorkerCompleted;

            //Competitors = tmpCompetitors();

            _dispatcherTimer = new DispatcherTimer();
            _dispatcherTimer.Tick += new EventHandler(dispatcherTimer_Tick);
            _dispatcherTimer.Interval = new TimeSpan(0, 0, 0, 0, 10);

            _clockTimer = new DispatcherTimer();
            _clockTimer.Tick += _clockTimer_Tick;
            _clockTimer.Interval = new TimeSpan(0, 0, 1);
            _clockTimer.Start();
        }

        void RacePage_Unloaded(object sender, RoutedEventArgs e)
        {
            //this.Drivers = null;
            //this.FinishedDrivers = null;
            //_mainWindow.SendToMulticastThread(string.Empty, MulticastDataType.NoRace);
            //RaceGrid.DataContext = null;
            //_decoderCommunicator.Dispose();
            //_dispatcherTimer.Stop();
        }

        void _clockTimer_Tick(object sender, EventArgs e)
        {
            lblComputerTime.Text = DateTime.Now.ToString(@"HH:mm:ss");
        }

        public RacePage(MainWindow m, Heat heat)
        {
            InitializeComponent();
            this.DataContext = this;
            _mainWindow = m;

            this.Unloaded += RacePage_Unloaded;

            HeatDuration = heat.Duration;
            HeatTitle = string.Format("{0} | {1} - {2}", heat.Event.EventName, heat.RaceClass.Name, heat.Name);

            Drivers = heat.HeatCompetitors;
            FinishedDrivers = new ObservableCollectionExt<HeatCompetitor>();

            RaceGrid.DataContext = Drivers;

            TimeSpan s = new TimeSpan(0, HeatDuration, 0);

            lblTimer.Content = s.ToString(@"mm\:ss\.fff");

            _decoderDifference = new TimeSpan();

            _decoderCommunicator = new AMBCommunicator(Properties.Settings.Default.DecoderIPAddress, Properties.Settings.Default.DecoderTcpPort);
            _decoderCommunicator.WorkerReportsProgress = true;
            _decoderCommunicator.WorkerSupportsCancellation = true;
            _decoderCommunicator.ProgressChanged += _decoderListener_ProgressChanged;
            _decoderCommunicator.RunWorkerCompleted += _decoderListener_RunWorkerCompleted;

            //Competitors = tmpCompetitors();

            _dispatcherTimer = new DispatcherTimer();
            _dispatcherTimer.Tick += new EventHandler(dispatcherTimer_Tick);
            _dispatcherTimer.Interval = new TimeSpan(0, 0, 0, 0, 1);

            _clockTimer = new DispatcherTimer();
            _clockTimer.Tick += _clockTimer_Tick;
            _clockTimer.Interval = new TimeSpan(0, 0, 1);
            _clockTimer.Start();

            _mainWindow.SendToMulticastThread(heat, MulticastDataType.HeatObject);
        }

        void RacePage_Loaded(object sender, RoutedEventArgs e)
        {
            if (!_dispatcherTimer.IsEnabled)
            {
                Dialogs.PreRaceDialog prd = new Dialogs.PreRaceDialog(this);
                prd.Owner = ParentWindow;
                prd.ShowDialog();

                TimeSpan s = new TimeSpan(0, HeatDuration, 0);

                lblTimer.Content = s.ToString(@"mm\:ss\.fff");

                Heat heat = new Heat() { Duration = HeatDuration, Name = HeatTitle, Elapsed = new TimeSpan(0, 0, 0).ToString(), HeatCompetitors = Drivers };

                _mainWindow.SendToMulticastThread(heat, MulticastDataType.HeatObject);
            }
        }

        /// <summary>
        /// Performs the StartRace routine
        /// </summary>
        private void StartRace()
        {
            if (!_decoderCommunicator.CancellationPending && !_dispatcherTimer.IsEnabled)
            {
                try
                {
                    TimeSpan durationLeft = new TimeSpan(0, HeatDuration, 0).Subtract(this.TimeElapsed);

                    _stopTimeStamp = DateTime.Now.Add(durationLeft);

                    _decoderCommunicator.RunWorkerAsync(AMBCommand.GetTime());
                    WriteToConsole("Information: Kopplar upp mot decoder.");

                    btnStart.IsEnabled = false;
                    btnStop.IsEnabled = false;
                    btnPause.IsEnabled = true;

                    _mainWindow.SetAllNavigationButtons(false);
                }
                catch (Exception ex)
                {
                    WriteToConsole(string.Format("Intern fel: {0}", ex.Message));

                    btnStart.IsEnabled = true;
                    btnStop.IsEnabled = false;
                    btnPause.IsEnabled = false;

                    _mainWindow.SetAllNavigationButtons(true);
                }
            }
        }

        /// <summary>
        /// Performs the PauseRace routine
        /// </summary>
        private void PauseRace()
        {
            // Stop the timer
            _dispatcherTimer.Stop();

            _decoderCommunicator.Disconnect();

            WriteToConsole("Race pausat!");

            btnPause.IsEnabled = false;
            btnStop.IsEnabled = true;
            btnStart.IsEnabled = true;

            UpdateTimeElapsed();

            _mainWindow.SendToMulticastThread(TimeElapsed, MulticastDataType.Pause);
        }

        /// <summary>
        /// Performs the StopRace routine
        /// </summary>
        private void StopRace()
        {
            // Stop the timer
            _dispatcherTimer.Stop();

            _decoderCommunicator.CancelAsync();

            btnPause.IsEnabled = false;
            btnStart.IsEnabled = false;
            btnStop.IsEnabled = false;

            _mainWindow.SetAllNavigationButtons(true);

            _mainWindow.SendToMulticastThread(TimeElapsed, MulticastDataType.Stop);
        }

        void _decoderListener_RunWorkerCompleted(object sender, System.ComponentModel.RunWorkerCompletedEventArgs e)
        {
            if (_dispatcherTimer.IsEnabled)
            {
                _decoderCommunicator.RunWorkerAsync();
            }
            else
            {
                WriteToConsole("Information: Kopplar från dekoder.");
                btnStart.IsEnabled = (Drivers.Count(d => !d.IsFinished) > 0) || Drivers.Count() == 0;
                btnPause.IsEnabled = false;

                if (IsRaceFinished)
                {
                    btnStop.IsEnabled = false;
                    btnStart.IsEnabled = false;
                    _mainWindow.SetAllNavigationButtons(true);
                }
            }
        }

        async void _decoderListener_ProgressChanged(object sender, System.ComponentModel.ProgressChangedEventArgs e)
        {
            if (e.UserState == null)
                return;

            if (e.ProgressPercentage.Equals(1))
                WriteToConsole("Information: Mottog meddelande vars CRC ej kunde valideras");

            try
            {
                AMB.TransferObject returnObj = e.UserState as AMB.TransferObject;

                switch (returnObj.MessageType)
                {
                    case AMBMessageType.STATUS:
                        try
                        {
                            AMB.Status obj = new AMB.Status(returnObj.Data);

                            DecoderNoise = obj.Noise;
                            DecoderTemperature = obj.Temperature;
                            DecoderVoltage = obj.InputVoltage;

                            UpdateTimeElapsed();

                            Heat heat = new Heat() { Duration = HeatDuration, Elapsed = TimeElapsed.ToString(), HeatCompetitors = Drivers, Name = HeatTitle, IsRunning = _dispatcherTimer.IsEnabled };

                            _mainWindow.SendToMulticastThread(heat, MulticastDataType.HeatObject);
                        }
                        catch (InvalidCastException)
                        {
                            // TODO: Implement logging
                            //MessageBox.Show(ex.Message, "Fel", MessageBoxButtons.OK, MessageBoxIcon.Error);
                        }
                        catch (Exception)
                        {
                            // TODO: Implement logging
                            //MessageBox.Show(ex.Message, "Fel", MessageBoxButtons.OK, MessageBoxIcon.Error);
                        }
                        break;
                    case AMBMessageType.PASSING:
                        try
                        {
                            AMB.Passing obj = new AMB.Passing(returnObj.Data);

                            // Process the passing only if the timer is running.
                            if (obj != null && _dispatcherTimer.IsEnabled)
                                await ProcessPassing(obj);
                        }
                        catch (Exception ex)
                        {
                            WriteErrorToConsole(ex.Message);
                            // TODO: Implement logging
#if DEBUG
                            throw ex;
#endif
                        }
                        break;

                    case AMBMessageType.GET_TIME:
                        try
                        {
                            AMB.GetTime obj = new AMB.GetTime(returnObj.Data);
                            if (obj != null && obj.RTCTime != null)
                            {
                                _decoderDifference = (DateTime.Now - obj.RTCTime);
                                if (!_decoderTimeSynchronized)
                                {
                                    WriteToConsole(string.Format("Synkroniserar klockor... ({0})", _decoderDifference));
                                    _decoderTimeSynchronized = true;
                                }

                                RunRace();
                            }
                        }
                        catch (Exception ex)
                        {
                            WriteErrorToConsole(ex.Message);
                            // TODO: Implement logging
#if DEBUG
                            throw ex;
#endif
                        }
                        break;
                }
            }
            catch (Exception ex)
            {
                WriteErrorToConsole(ex.Message);
                // TODO: Implement logging
#if DEBUG
                throw ex;
#endif
            }
        }

        private async Task ProcessPassing(AMB.Passing passing)
        {
            try
            {
                if (!_dispatcherTimer.IsEnabled)
                    return;

                HeatCompetitor competitor = Drivers.FirstOrDefault(c => c.TransponderId == passing.TransponderId);

                if (competitor == null)
                {
                    Transponder transponder = null;

                    await DatabaseHelper.DoWithDBAsync<DBContext>(db =>
                    {
                        transponder = db.Transponders.FirstOrDefault(t => t.TransponderId == passing.TransponderId);

                        if (transponder != null)
                        {

                            if (transponder.Driver != null)
                            {
                                competitor = new HeatCompetitor(transponder.Driver, transponder.TransponderId);

                                if (transponder.Driver.Club != null)
                                {
                                    string str = transponder.Driver.Club.FullName;
                                }
                            }
                        }
                        else
                        {
                            competitor = new HeatCompetitor() { TransponderId = passing.TransponderId };
                        }
                    });

                    //AsyncHelper.EnsureUIThread(() =>
                    //   {
                    Drivers.Add(competitor);
                    //});
                }

                if (competitor.Laps == null)
                    competitor.Laps = new ObservableCollectionExt<Lap>();

                if (!competitor.IsFinished)
                {
                    //_competitors.Where(c => c.Transponder.Id == (int)passing.TransponderId).FirstOrDefault().AddLap(_startTimeStamp.Subtract(_decoderDifference), passing.PassingTime, passing.SignalStrength - DecoderNoise, passing.Voltage);
                    competitor.AddLap(_startTimeStamp.Subtract(_decoderDifference), passing.PassingTime, passing.SignalStrength - DecoderNoise, passing.Voltage, passing.Hits);

                    SortSetPositionAndBeep();

                    if (passing.PassingTime.Add(_decoderDifference) >= _stopTimeStamp)
                    {
                        //_competitors.Where(c => c.Transponder.Id == (int)passing.TransponderId).FirstOrDefault().IsFinished = true;

                        competitor.IsFinished = true;

                        //_competitors.Where(c => c.Transponder.Id == (int)passing.TransponderId).FirstOrDefault().FinishTime = (passing.PassingTime.Add(_decoderDifference) - _stopTimeStamp).TotalSeconds;

                        competitor.FinishTime = (passing.PassingTime.Add(_decoderDifference) - _stopTimeStamp).TotalSeconds;

                    }

                    try
                    {
                        if (competitor.AverageLapTime > 0)
                        {
                            int totalSeconds = HeatDuration * 60;

                            competitor.EstimatedResult = (totalSeconds / competitor.AverageLapTime).ToString("F0");
                        }
                    }
                    catch (Exception ex)
                    {
                        // TODO: Log error
                        WriteErrorToConsole("Ett fel inträffade när estimerat resultat skulle beräknas.");
                    }
                }

                if (competitor.StartNumber.HasValue)
                    AddStartNumberToPassings(competitor.StartNumber.Value);

                _mainWindow.SendToMulticastThread(Drivers, MulticastDataType.Competitors);

                if (Drivers.Count > 0 && Drivers.All(c => c.IsFinished))
                    FinishRace();
            }
            catch (Exception ex)
            {
                WriteErrorToConsole(ex.Message);

                // TODO: Implement logging
#if DEBUG
                throw ex;
#endif
            }
        }

        private void AddStartNumberToPassings(int startNumber)
        {
            this._passings.Enqueue(startNumber.ToString());

            icPassingsStack.ItemsSource = this._passings.ToArray().Reverse();
        }

        private void SortSetPositionAndBeep()
        {
            Drivers.SortDescending(t => t.NumberOfLaps, r => (r.PreviousLap ?? new Lap(0.0, _startTimeStamp, 0.0, 0.0, 0.0)).PassingTime);

            int pos = 1;

            foreach (HeatCompetitor c in Drivers)
            {
                Drivers.Where(t => t.TransponderId == c.TransponderId).FirstOrDefault().Position = pos;

                pos++;
            }

            SoundPlayer.PlaySound(1, 80, false);
        }

        private void RunRace()
        {
            if (_dispatcherTimer.IsEnabled)
                return;

            if (_finishTimeout == 0)
            {
                WriteToConsole("Fel: Slut-timout har inte ställts in.");
                return;
            }

            if (!IsDecoderNoiseSet)
                WriteToConsole("Information: Dekoder har ännu inte rapporterat brusnivå.");

            if (IsRaceFinished)
                return;

            if (IsFinished)
                return;

            if (!_decoderTimeSynchronized)
                return;

            _startTimeStamp = DateTime.Now.Subtract(TimeElapsed);
            _stopTimeStamp = _startTimeStamp.AddMinutes(HeatDuration);
            _finishTimeStamp = _stopTimeStamp.AddSeconds(_finishTimeout);

            _dispatcherTimer.IsEnabled = true;

            WriteToConsole("Race startar!");

            btnPause.IsEnabled = true;
            btnStop.IsEnabled = false;

            SoundPlayer.PlaySound(1, 1000, true);

            _mainWindow.SendToMulticastThread(new StartCommand() { HeatDurationLeft = new TimeSpan(0, HeatDuration, 0).Subtract(TimeElapsed) }, MulticastDataType.Start);

        }

        /// <summary>
        /// Eventhandler for every tick the dispatchTimer count.
        ///  - Check if the timer is running.
        ///  - Calculate the timespans to STOP and FINISH time.
        ///  - If more or equal to STOP time, sound the alarm, once. Set the timer foreground to red.
        ///  - If more or equal to FINISH time. Perform FinishRace routine.
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void dispatcherTimer_Tick(object sender, EventArgs e)
        {
            UpdateTimeElapsed();

            TimeSpan o = _stopTimeStamp - _startTimeStamp.Add(TimeElapsed);

            TimeSpan t = DateTime.Now.Subtract(_finishTimeStamp);

            TimeSpan n = new TimeSpan(0);

            if (o <= n)
            {
                if (!_stopSignalSounded)
                {
                    SoundPlayer.PlaySound(1, 2000, true);
                    _stopSignalSounded = !_stopSignalSounded;

                    lblTimer.Foreground = Brushes.Red;
                }
            }

            if (t >= n)
                FinishRace();

            lblTimer.Content = o.ToString(@"mm\:ss\.fff");
        }

        /// <summary>
        /// Performs the "Finish" routine.
        ///  - Stop the communication with the AMB decoder
        ///  - Stop the timer
        ///  - Disable the PAUSE and STOP buttons
        ///  - Enable all the locked buttons in the MainWindow
        ///  - Set the foreground color on all cells of the rows in the DataGrid to Brushes.LightGray
        ///  - TODO: Pop up the 'corrections'-page for results corrections.
        /// </summary>
        private void FinishRace()
        {
            _dispatcherTimer.Stop();

            IsFinished = true;
            // Cancel the AMBListener
            _decoderCommunicator.CancelAsync();

            _mainWindow.SendToMulticastThread(string.Empty, MulticastDataType.Stop);

            // Disable all buttons
            btnStart.IsEnabled = false;
            btnPause.IsEnabled = false;
            btnStop.IsEnabled = false;

            SoundPlayer.PlaySound(0, 60, false);
            SoundPlayer.PlaySound(0, 60, false);
            SoundPlayer.PlaySound(0, 2000, false);

            //// Fetch the mainform and use it to set all buttons to enabled.
            //MainForm m = this.ParentForm as MainForm;
            _mainWindow.SetAllNavigationButtons(true);

            //// Click the "print"-button
            //btn_print_Click(null, null);

            // Set the foregroundcolor on all cells of the rows of the datagrid to Color.LightGray
            RaceGrid.Foreground = Brushes.LightSlateGray;

            // Let the UI know that the race is finished.
            OnPropertyChanged("RaceFinished");
            OnPropertyChanged("IsFinished");

            btnPrint_Click(null, null);
        }

        [field: NonSerialized]
        public event PropertyChangedEventHandler PropertyChanged;

        protected void OnPropertyChanged(string name)
        {
            PropertyChangedEventHandler handler = PropertyChanged;
            if (handler != null)
            {
                handler(this, new PropertyChangedEventArgs(name));
            }
        }

        private void btnStart_Click(object sender, RoutedEventArgs e)
        {
            StartRace();
        }

        private void btnPause_Click(object sender, RoutedEventArgs e)
        {
            PauseRace();
        }

        private void btnStop_Click(object sender, RoutedEventArgs e)
        {
            _finishTimeStamp = DecoderTime;

            StopRace();
        }

        private void ContextMenu_Penalty_Click(object sender, RoutedEventArgs e)
        {
            try
            {
                //Get the clicked MenuItem
                var menuItem = (MenuItem)sender;

                //Get the ContextMenu to which the menuItem belongs
                var contextMenu = (ContextMenu)menuItem.Parent;

                //Find the placementTarget
                var item = (DataGrid)contextMenu.PlacementTarget;

                //Get the underlying item, that you cast to your object that is bound
                //to the DataGrid (and has subject and state as property)
                HeatCompetitor competitor = (HeatCompetitor)item.SelectedCells[0].Item;

                DoManualCount(competitor.TransponderId, true, true);
            }
            catch (Exception ex)
            {
                WriteErrorToConsole(ex.Message);
                // TODO: Implement logging
            }
        }

        private void ContextMenu_Add_Click(object sender, RoutedEventArgs e)
        {
            try
            {
                //Get the clicked MenuItem
                var menuItem = (MenuItem)sender;

                //Get the ContextMenu to which the menuItem belongs
                var contextMenu = (ContextMenu)menuItem.Parent;

                //Find the placementTarget
                var item = (DataGrid)contextMenu.PlacementTarget;

                //Get the underlying item, that you cast to your object that is bound
                //to the DataGrid (and has subject and state as property)
                HeatCompetitor competitor = (HeatCompetitor)item.SelectedCells[0].Item;
                DoManualCount(competitor.TransponderId, false);
            }
            catch (Exception ex)
            {
                WriteErrorToConsole(ex.Message);
                // TODO: Implement logging
            }
        }

        private void ContextMenu_Remove_Click(object sender, RoutedEventArgs e)
        {
            try
            {
                //Get the clicked MenuItem
                var menuItem = (MenuItem)sender;

                //Get the ContextMenu to which the menuItem belongs
                var contextMenu = (ContextMenu)menuItem.Parent;

                //Find the placementTarget
                var item = (DataGrid)contextMenu.PlacementTarget;

                //Get the underlying item, that you cast to your object that is bound
                //to the DataGrid (and has subject and state as property)
                HeatCompetitor competitor = (HeatCompetitor)item.SelectedCells[0].Item;
                DoManualCount(competitor.TransponderId, true);
            }
            catch (Exception ex)
            {
                WriteErrorToConsole(ex.Message);
                // TODO: Implement logging
            }
        }

        private void ContextMenu_Disqualify_Click(object sender, RoutedEventArgs e)
        {
            try
            {
                //Get the clicked MenuItem
                var menuItem = (MenuItem)sender;

                //Get the ContextMenu to which the menuItem belongs
                var contextMenu = (ContextMenu)menuItem.Parent;

                //Find the placementTarget
                var item = (DataGrid)contextMenu.PlacementTarget;

                //Get the underlying item, that you cast to your object that is bound
                //to the DataGrid (and has subject and state as property)
                HeatCompetitor competitor = (HeatCompetitor)item.SelectedCells[0].Item;
                competitor.IsFinished = true;
            }
            catch (Exception ex)
            {
                WriteErrorToConsole(ex.Message);
                // TODO: Implement logging
            }
        }

        private void btnPrint_Click(object sender, RoutedEventArgs e)
        {
            try
            {
                PDFHelper.CreateHeatResult(Drivers, HeatTitle);
            }
            catch (Exception ex)
            {
                // TODO: Something with this exception
            }
        }

        private void btnCorrection_Click(object sender, RoutedEventArgs e)
        {
            Dialogs.HeatCorrectionDialog prd = new Dialogs.HeatCorrectionDialog(this);
            prd.Owner = ParentWindow;
            prd.ShowDialog();
        }

        private void MenuItem_Click(object sender, RoutedEventArgs e)
        {

        }

        private void RaceGrid_SelectionChanged(object sender, SelectionChangedEventArgs e)
        {
            HeatCompetitor selectedCompetitor = ((DataGrid)sender).SelectedItem as HeatCompetitor;

            if (selectedCompetitor != null)
            {
                SelectedDriverInformation.Visibility = System.Windows.Visibility.Visible;
                SelectedDriverInformation.SetInformation(selectedCompetitor);
            }
        }
    }
}
